/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package sync.model;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

/**
 *
 * @author yarden
 */
public class Catalog {
    public final String CATALOG_TAG         = "catalog";
    public final String USER_TAG            = "user";
    public final String ASSIGNMENT_TAG      = "assignment";
    public final String ASSIGNED_DATE_TAG   = "assigned_date";
    public final String DUE_DATE_TAG        = "due_date";
    public final String MODIFIED_DATE_TAG   = "modified_date";
    public final String SCHEMA_TAG          = "schema";
    public final String FILENAME_TAG        = "filename";
    public final String ITEM_TAG            = "item";
    
    public final String ID_ATTRIBUTE        = "id";
    public final String NAME_ATTRIBUTE      = "name";
    public final String SYNC_ATTRIBUTE      = "last_sync";
    public final String FILE_ATTRIBUTE      = "file";
    public final String STATUS_ATTRIBUTE    = "status";
    
    private UserInfo user = new UserInfo();
    
    
    public UserInfo getUserInfo() {
        return user;
    }
    
    public void updateCatalog(String filename) {
        boolean found = false;
        XMLInputFactory inputFactory = getInputFactory();
        XMLOutputFactory outputFactory = getOutputFactory();
        
        try {
            XMLEventReader reader = inputFactory.createXMLEventReader(new FileInputStream(filename));
            XMLEventWriter writer = outputFactory.createXMLEventWriter(new FileOutputStream(filename+".tmp"));
            
            while (reader.hasNext()) {
               
                XMLEvent event = reader.nextEvent();
                if (isCurrentUserEvent(event)) {
                     found = true;
                     
                    // write user info
                    writeUserCatalog(writer);
                    
                    // skip rest of the user's info that is on the file
                    while (reader.hasNext()) {
                        event = reader.nextEvent();
                        if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(USER_TAG))
                            break;
                    }
                } 
                else if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(CATALOG_TAG)) {
                    if (!found) {
                        writeUserCatalog(writer);
                        writer.add(event);
                    }
                }
                else { // write event 
                    writer.add(event);
                }
                    
            }
  
            writer.flush();
            
            reader.close();
            writer.close();
            
            // replace files
            File old = new File(filename);
            old.delete();
            
            File tmp = new File(filename+".tmp");
            tmp.renameTo(old);
        } catch (Exception e) {
             Logger.getLogger(Catalog.class.getName()).log(Level.SEVERE, "Error while updating catalog", e);
        }
    }
     
   
    private void writeUserCatalog(XMLEventWriter writer) {
        XMLEventFactory factory = XMLEventFactory.newInstance();
        
        XMLEvent event;    
        
        try {
            // user start tag
            event = factory.createStartElement("", "", USER_TAG);
            writer.add(event);
        
            event = factory.createAttribute(NAME_ATTRIBUTE, user.getUsername());
            writer.add(event);
            
            event = factory.createAttribute(SYNC_ATTRIBUTE, user.getLastSync());
            writer.add(event);
            
            event = factory.createCharacters("\n");
            writer.add(event);
            
            // write assignments
            for (Iterator<Assignment> i = user.assignments.iterator(); i.hasNext();) {
                Assignment assignment = i.next();
                
                event = factory.createStartElement("", "", ASSIGNMENT_TAG);
                writer.add(event);
                
                event = factory.createAttribute(ID_ATTRIBUTE, assignment.getId());
                writer.add(event);
            
                event = factory.createCharacters("\n");
                writer.add(event);                
                
                // assigned date
                event = factory.createStartElement("", "", ASSIGNED_DATE_TAG);
                writer.add(event);
                 
                event = factory.createCharacters(assignment.getAssigned());
                writer.add(event);
                
                event = factory.createEndElement("", "", ASSIGNED_DATE_TAG);
                writer.add(event);
                
                event = factory.createCharacters("\n");
                writer.add(event);
                
                // due date
                event = factory.createStartElement("", "", DUE_DATE_TAG);
                writer.add(event);
                 
                event = factory.createCharacters(assignment.getDue());
                writer.add(event);
                
                event = factory.createEndElement("", "", DUE_DATE_TAG);
                writer.add(event);
            
                event = factory.createCharacters("\n");
                writer.add(event);
                
                // modified date
                event = factory.createStartElement("", "", MODIFIED_DATE_TAG);
                writer.add(event);
                 
                event = factory.createCharacters(assignment.getModified());
                writer.add(event);
                
                event = factory.createEndElement("", "", MODIFIED_DATE_TAG);
                writer.add(event);
            
                event = factory.createCharacters("\n");
                writer.add(event);
                
                // schema
                event = factory.createStartElement("", "", SCHEMA_TAG);
                writer.add(event);
                 
                event = factory.createCharacters(assignment.getSchema());
                writer.add(event); 
                
                event = factory.createEndElement("", "", SCHEMA_TAG);
                writer.add(event);
            
                event = factory.createCharacters("\n");
                writer.add(event);
                
                // Items
                for (Iterator<AssignmentItem> j = assignment.getItems().iterator(); j.hasNext();) {
                    AssignmentItem item = j.next();
                    
                    event = factory.createStartElement("", "", ITEM_TAG);
                    writer.add(event);
                 
                    event = factory.createAttribute(ID_ATTRIBUTE, item.getId());
                    writer.add(event);
                    
                    event = factory.createAttribute(NAME_ATTRIBUTE, item.getName());
                    writer.add(event);
                    
//                    event = factory.createAttribute(FILE_ATTRIBUTE, item.getFilename());
//                    writer.add(event);
                    
                    event = factory.createAttribute(STATUS_ATTRIBUTE, item.getStatus().toString());
                    writer.add(event);
                    
                    
                    event = factory.createEndElement("", "", ITEM_TAG);
                    writer.add(event);
            
                    event = factory.createCharacters("\n");
                    writer.add(event);
                }
                
                event = factory.createEndElement("", "", ASSIGNMENT_TAG);
                writer.add(event);
                
                            
                event = factory.createCharacters("\n");
                writer.add(event);
                                
            }
            
            // user end tag
            event = factory.createEndElement("", "", USER_TAG);
            writer.add(event);
            
            event = factory.createCharacters("\n");
            writer.add(event);
        
        } catch (XMLStreamException ex) {
            Logger.getLogger(Catalog.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        
    }
    
    public void readUserCatalog(String username, String filename)
    {
        this.user = new UserInfo();
        this.user.setUsername(username);
        
        XMLInputFactory factory = getInputFactory();
        
        try {
            XMLEventReader reader = factory.createXMLEventReader(new FileInputStream(filename));
            while (reader.hasNext()) {
               XMLEvent event = reader.nextEvent();
               if (isCurrentUserEvent(event)) {  
                   user.setLastSync(event.asStartElement().getAttributeByName(new QName(SYNC_ATTRIBUTE)).getValue()); 
                    // parse user info
                    while (reader.hasNext()) {
                        event = reader.nextEvent();

                        
                        if (event.isStartElement()) {
                            StartElement elem = event.asStartElement();
                            String name = elem.getName().getLocalPart();
                            if (name.equals(ASSIGNMENT_TAG)) {
                                // read assignment
                                readAssignment(elem, reader);
                            }
                        }
                       
                        else if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(USER_TAG))
                            return;
                        
                    }
                  
               }
            }
        } 
        catch (FileNotFoundException e) {
             Logger.getLogger(Catalog.class.getName()).log(Level.SEVERE, "Catalog file is missing", e); 
        }     
        catch (XMLStreamException e) {
             Logger.getLogger(Catalog.class.getName()).log(Level.SEVERE, "Error reading catalog file", e); 
        } 
        
    }
    

    private void readAssignment(StartElement start, XMLEventReader reader) {
         String id = start.getAttributeByName(new QName(ID_ATTRIBUTE)).getValue();
         
         Assignment assignment = new Assignment(id);
         
         List<Assignment> assignments = user.getAssignments();
         try {
            while (reader.hasNext()) {
                 XMLEvent event = reader.nextEvent();
                 if (event.isStartElement()) {
                     start = event.asStartElement();
                     String tag = start.getName().getLocalPart();
                     
                     if (tag.equals(ASSIGNED_DATE_TAG)) {
                         event = reader.nextEvent();
                         if (event.getEventType() == event.CHARACTERS) {
                             assignment.setAssigned(event.asCharacters().getData());
                         }
                     }
                     else if (tag.equals(DUE_DATE_TAG)) {
                         event = reader.nextEvent();
                         if (event.getEventType() == event.CHARACTERS) {
                             assignment.setDue(event.asCharacters().getData());
                         }
                     }
                     else if (tag.equals(MODIFIED_DATE_TAG)) {
                         event = reader.nextEvent();
                         if (event.getEventType() == event.CHARACTERS) {
                             assignment.setModified(event.asCharacters().getData());
                         }
                     }
                     else if (tag.equalsIgnoreCase(SCHEMA_TAG)) {
                         event = reader.nextEvent();
                         if (event.getEventType() == event.CHARACTERS) {
                             assignment.setSchema(event.asCharacters().getData());
                         }
                     }
                     else if (tag.equals(ITEM_TAG)) {
                        assignment.addItem(readAssignmentItem(start, reader));
                     }
                 }
                 else if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(ASSIGNMENT_TAG))
                     break;
            }
            assignments.add(assignment);
         }
         catch (XMLStreamException e) {
             Logger.getLogger(Catalog.class.getName()).log(Level.SEVERE, "Error reading catalog file", e); 
        }     
    }

    private AssignmentItem readAssignmentItem(StartElement start, XMLEventReader reader) {
        String itemId = start.getAttributeByName(new QName(ID_ATTRIBUTE)).getValue();
        String itemName = start.getAttributeByName(new QName(NAME_ATTRIBUTE)).getValue();
//        String itemFile = start.getAttributeByName(new QName(FILE_ATTRIBUTE)).getValue();
        String itemStatus = start.getAttributeByName(new QName(STATUS_ATTRIBUTE)).getValue();
        
        AssignmentItem item = new AssignmentItem(itemId);
        item.setName(itemName);
        //item.setFilename(itemFile);
        item.setStatus(Assignment.Status.getStatus(itemStatus));
        
        // skip until end of tag
        skip(reader, ITEM_TAG);
        return item;
    }
    
    private void skip(XMLEventReader reader, String tag) {   
        try {
             while (reader.hasNext()) {
                XMLEvent event = reader.nextEvent();
                if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(tag))
                    return;
             }
        } catch (XMLStreamException ex) {
            Logger.getLogger(Catalog.class.getName()).log(Level.SEVERE, "Catalog parsing error", ex);
        }
    }
    
    private XMLInputFactory getInputFactory() {
        XMLInputFactory factory = null;
        try {
            factory = XMLInputFactory.newInstance();
            factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.TRUE);
            factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
            factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.TRUE);
            factory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
        } catch (Exception e) {
            Logger.getLogger(Catalog.class.getName()).log(Level.SEVERE, null, e);
        }
        return factory;
    }
    
    private XMLOutputFactory getOutputFactory() {
        return XMLOutputFactory.newInstance();
    }
    
    private boolean isCurrentUserEvent(XMLEvent event) {
        return event.isStartElement()
            && event.asStartElement().getName().getLocalPart().equals(USER_TAG)
            && event.asStartElement().getAttributeByName(new QName(NAME_ATTRIBUTE)).getValue().equals(user.getUsername());        
    }
    
    
}
